/*
 *	MIDAS Context Based Routing
 *	cbr_eth.cc
 *	Copyright (c) 2007 Bartlomiej Zolnierkiewicz
 *	MEAG IT PW
 *
 *	This code is more C-like than C++ since it is easier
 *	to implementent networking features this way and could
 *	be helpful in case if we convert CBR to pure C one day.
 */

#include <sys/types.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <string.h>
#include <stdlib.h>

#include "cbr_eth.h"

/**
 * Binds RAW Ethernet socket.
 *
 * @param s socket descriptor
 * @param i interface index
 *
 * @return 0 on success, -1 otherwise.
 */

int eth_socket_bind(int s, int i)
{
	struct sockaddr_ll addr;

	memset(&addr, 0, sizeof(addr));
	addr.sll_family = AF_PACKET;
	addr.sll_ifindex = i;

	if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		cbr_log("error: cannot bind() Ethernet socket (errno=%d): "
			"%s\n", errno, strerror(errno));
		return -1;
	}

	return 0;
}

/**
 * Creates RAW Ethernet socket.
 *
 * @param ifname interface name
 * @param ifindex interface index
 *
 * @return socket descriptor number on success, -1 otherwise.
 */

int eth_socket_create(const char *ifname, int &ifindex)
{
	struct ifreq ifr;

	int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

	// raw sockets require root priveleges or CAP_NET_RAW
	if (s == -1) {
		cbr_log("error: Ethernet socket() failed (errno=%d): %s\n",
			errno, strerror(errno));
		return -1;
	}

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));

	if (ioctl(s, SIOCGIFINDEX, &ifr) == -1) {
		cbr_log("error: cannot get Ethernet interface index "
			"(errno=%d): %s\n", errno, strerror(errno));
		close(s);
		return -1;
	}

	if (eth_socket_bind(s, ifr.ifr_ifindex) == -1) {
		close(s);
		return -1;
	}

	ifindex = ifr.ifr_ifindex;

	return s;
}

/*
static void dump_eth_header(void *buffer)
{
	unsigned char *dest_mac = (unsigned char *)buffer;
	unsigned char *src_mac = (unsigned char *)buffer + ETH_ALEN;
	int i;

	cbr_debug(" src MAC: ");
	for (i = 0; i < 6; i++)
		cbr_debug("%02x%s", src_mac[i], i != 5 ? ":" : "");
	cbr_debug("\n dest MAC: ");
	for (i = 0; i < 6; i++)
		cbr_debug("%02x%s", dest_mac[i], i != 5 ? ":" : "");
	cbr_debug("\n type: %04x\n", *(unsigned short *)(src_mac + ETH_ALEN));
}
*/

/**
 * Sends Ethernet frame.
 *
 * @param s socket descriptor
 * @param i interface index
 * @param src_mac source MAC address
 * @param dst_mac destination MAC address
 * @param eth_p protocol to be used
 * @param payload pointer to buffer containing payload
 *
 * Tries to send Ethernet frame of the total size (including header)
 * equal to ETH_FRAME_LEN (1514 bytes) from source MAC address @a src_mac to
 * the destination MAC address @a dst_mac through the socket descriptor @a s.
 *
 * If the payload is less than ETH_DATA_LEN bytes then the rest of
 * the sent bytes are zeroes.
 *
 * @return the  number of bytes sent on success, -1 otherwise.
 */

int eth_send(int s, int i, mac_addr *src_mac, mac_addr *dst_mac,
	     __u16 eth_p, void *payload)
{
	struct sockaddr_ll t_addr;		// target address
	void *buffer = malloc(ETH_FRAME_LEN);	// buffer for ethernet frame
	void *etherhead = buffer;		// pointer to ethenet header
	void *data = (unsigned char *)buffer + 14;// userdata in ethernet frame
	// another pointer to ethernet header
	struct ethhdr *eh = (struct ethhdr *)etherhead;
	int rc = 0;

	//if (cbr_log_addresses()) {
		cbr_log("SEND: source MAC=%s\n", mactostr(src_mac));
		cbr_log("SEND: destination MAC=%s\n", mactostr(dst_mac));
	//}

	if (buffer == NULL) {
		cbr_log("error: malloc() for outgoing Ethernet frame failed"
			" (errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	memset(buffer, 0, ETH_FRAME_LEN);

	// prepare sockaddr_ll
	memset(&t_addr, 0, sizeof(t_addr));

	t_addr.sll_family = PF_PACKET;
	t_addr.sll_protocol = htons(ETH_P_IP);	// anything, we don't a protocol
						// above ethernet layer
	t_addr.sll_ifindex  = i;		// index of the network device
	t_addr.sll_hatype   = ARPHRD_ETHER;	// ARP hardware identifier
	t_addr.sll_pkttype  = PACKET_OTHERHOST;	// target is another host
	t_addr.sll_halen    = ETH_ALEN;		// address length

	memcpy(t_addr.sll_addr, dst_mac, 6);	// MAC address

	// set the frame header
	memcpy(buffer, dst_mac, ETH_ALEN);
	memcpy((char *)buffer + ETH_ALEN, src_mac, ETH_ALEN);

	eh->h_proto = htons(eth_p);

	// copy payload
	memcpy(data, payload, ETH_DATA_LEN);

	if (cbr_log_eth_packet())
		dump_cbr_data(buffer, ETH_FRAME_LEN);

	rc = sendto(s, buffer, ETH_FRAME_LEN, 0,
			(struct sockaddr*)&t_addr, sizeof(t_addr));
	if (rc == -1) {
		cbr_log("error: sendto() failed for Ethernet frame "
			"(errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}
	
	cbr_log("  sent %d bytes with eth_send\n", rc);

	free(buffer);

	return rc;
}

/**
 * Receives Ethernet frame.
 *
 * @param s socket descriptor
 * @param payload pointer to buffer containing payload
 * @param src_mac pointer to buffer to put the source MAC address
 * @param eth_p protocol to be used
 *
 * Tries to receive Ethernet frame of the total size (including header)
 * equal to ETH_FRAME_LEN (1514 bytes) from the socket descriptor @a s.
 *
 * Source MAC address is decoded from the Ethernet header and put into
 * the buffer pointed by @a src_mac.  Payload is copied from the Ethernet
 * frame into the buffer pointed by @a payload.
 *
 * @return the number of bytes received on success, -1 otherwise.
 */

int eth_recv(int s, void *payload, mac_addr *src_mac, __u16 eth_p)
{
	void *buffer = malloc(ETH_FRAME_LEN);	// buffer for ethernet frame
	void *data = (unsigned char *)buffer + 14;// userdata in ethernet frame
	int len = 0;
	unsigned char *eth_proto = (unsigned char *)buffer + 2*ETH_ALEN;

	if (buffer == NULL) {
		cbr_log("error: malloc() for incoming Ethernet frame failed"
			" (errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	memset(buffer, 0, ETH_FRAME_LEN);

	len = recvfrom(s, buffer, ETH_FRAME_LEN, 0, NULL, NULL);

	if (len == -1) {
		free(buffer);
		cbr_log("error: recvfrom() failed for Ethernet frame "
			"(errno=%d): %s\n", errno, strerror(errno));
		return -1;
	}

	// get source MAC
	memcpy(src_mac, (char *)buffer + ETH_ALEN, ETH_ALEN);

	if (cbr_log_addresses())
		cbr_log("RECV: source MAC=%s\n", mactostr(src_mac));

	if (cbr_log_eth_packet())
		dump_cbr_data(buffer, ETH_FRAME_LEN);

	// not our frame, just discard it
	if (ntohs(*(__u16 *)eth_proto) != eth_p) {
		if (cbr_log_discarded_packet())
			cbr_log(" discarding Ethernet frame - bad protocol"
				" (%x)\n", ntohs(*(__u16 *)eth_proto));
		free(buffer);
		return 0; // it is important to return success here
	}

	if (eth_p == ETH_P_CBR && len != ETH_FRAME_LEN) {
		free(buffer);
		cbr_log("error: bad Ethernet frame size (%d)\n", len);
		return -1;
	}

	// get payload
	memcpy(payload, data, ETH_DATA_LEN);

	free(buffer);

	return len;
}
